fix(groovy): emit ClassRelation for interface inheritance in both engines#1158
Conversation
…ines `interface X extends Y, Z` produced no class-graph edges because both extractors pushed only a Definition for the interface and never inspected the unnamed `extends_interfaces` child. The Rust handler now reuses `collect_interfaces` (which already recurses into `type_list`); the TS handler gains an equivalent helper that mirrors the same traversal. Closes #1115
|
Claude finished @carlos-alm's task —— View job Reviewing PR: fix(groovy): emit ClassRelation for interface inheritance in both enginesTodo List
Root Cause AnalysisProblem Being Solved: Why This Approach:
Risk Assessment: LOW - Purely additive change to extraction logic, no behavioral modifications to existing functionality. Backlog Compliance
Code Quality AssessmentImplementation Strengths
Technical AnalysisRust Implementation ( for i in 0..node.child_count() {
let Some(child) = node.child(i) else { continue };
if child.kind() == "extends_interfaces" {
collect_interfaces(&child, &iface_name, source, symbols);
break;
}
}✅ Efficient early termination with TypeScript Implementation ( for (let i = 0; i < node.childCount; i++) {
const child = node.child(i);
if (child && child.type === 'extends_interfaces') {
collectGroovyParentInterfaces(child, ifaceName, node.startPosition.row + 1, ctx);
break;
}
}✅ Mirrors Rust pattern exactly Minor Observations
Critical ConcernsNone found. This is a well-executed bug fix with proper testing and engine parity. Final Recommendation
Excellent work - this demonstrates deep understanding of both the tree-sitter grammar and the codegraph extraction pipeline. |
Greptile SummaryThis PR fixes a missing-edge bug where
Confidence Score: 5/5Safe to merge — a focused, additive extraction fix with no changes to downstream consumers or the graph builder. The change is purely additive: it adds child scanning in two handlers and a new TS helper, both narrowly scoped to No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["interface_declaration node"] --> B{find child\nkind == 'extends_interfaces'}
B -- found --> C["extends_interfaces node"]
B -- not found --> Z["emit Definition only"]
C --> D["collect_interfaces / collectGroovyParentInterfaces"]
D --> E{child.type}
E -- type_identifier\nidentifier\nscoped_type_identifier --> F["emit ClassRelation\n{ name: X, implements: Y, line }"]
E -- generic_type --> G["read child(0).text\nemit ClassRelation"]
E -- type_list --> H["recurse with child\n(re-evaluates start line)"]
H --> E
E -- other --> I["skip"]
Reviews (7): Last reviewed commit: "Merge branch 'main' into fix/1115-groovy..." | Re-trigger Greptile |
| function collectGroovyParentInterfaces( | ||
| parent: TreeSitterNode, | ||
| name: string, | ||
| line: number, | ||
| ctx: ExtractorOutput, | ||
| ): void { | ||
| for (let i = 0; i < parent.childCount; i++) { | ||
| const child = parent.child(i); | ||
| if (!child) continue; | ||
| switch (child.type) { | ||
| case 'type_identifier': | ||
| case 'identifier': | ||
| case 'scoped_type_identifier': { | ||
| ctx.classes.push({ name, implements: child.text, line }); | ||
| break; | ||
| } | ||
| case 'generic_type': { | ||
| const inner = child.child(0)?.text; | ||
| if (inner) ctx.classes.push({ name, implements: inner, line }); | ||
| break; | ||
| } | ||
| case 'type_list': | ||
| collectGroovyParentInterfaces(child, name, line, ctx); | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
Line number diverges from Rust engine on multi-line declarations
collectGroovyParentInterfaces receives line once (the interface declaration's startPosition.row + 1) and forwards the same value through every recursive call into type_list children. The Rust collect_interfaces instead evaluates start_line(interfaces) at each recursive invocation, so it tracks the actual start line of whatever node (extends_interfaces → type_list) is being processed at that moment.
For a multi-line interface — e.g. interface X\n extends Y, Z {} — the Rust engine would emit line 2 for the relation, while the TS engine always emits line 1. This is currently benign (the downstream HIERARCHY_SOURCE_KINDS filter doesn't include interface), but it means the two engines will produce divergent ClassRelation.line values once that filter is extended.
There was a problem hiding this comment.
Fixed in 732c82d — collectGroovyParentInterfaces no longer accepts a fixed line parameter; it now derives line = parent.startPosition.row + 1 at each recursion level, matching Rust's collect_interfaces which re-evaluates start_line(interfaces) per call. Added an engine-parity guard test (interface X\n extends Y, Z {} → line 2) in both Rust and WASM test suites so this divergence cannot regress.
Codegraph Impact Analysis5 functions changed → 4 callers affected across 2 files
|
…1158) Align the TypeScript collectGroovyParentInterfaces helper with the Rust collect_interfaces helper, which re-evaluates start_line(interfaces) at each recursive invocation. Previously the WASM extractor forwarded the interface declaration's start line through every recursion level, so a multi-line interface (e.g. interface X\n extends Y, Z {}) produced line=1 in WASM but line=2 in native — the two engines must produce identical results. Add a multi-line line-parity test in both engines to guard against future regressions.
Summary
interface X extends Y, Zproduced no class-graph edges because both Groovy extractors pushed a singleDefinitionfor the interface but never inspected the unnamedextends_interfaceschild.collect_interfaceshelper (which already recurses intotype_list); the TS handler gains an equivalentcollectGroovyParentInterfaceshelper that mirrors the same traversal.ClassRelation { name: X, implements: Y, line }for each parent interface — consistent with the existingcollect_interfaceshelper, with class-implements behavior, and with the downstreamIMPLEMENTS_TARGET_KINDSfilter that already accepts interface targets.Notes
tree-sitter-groovy0.1.x exposes parent interfaces as an unnamedextends_interfaceschild ofinterface_declaration(not a field), distinct from class declarations which use theinterfacesfield that wrapssuper_interfaces → type_list.HIERARCHY_SOURCE_KINDSset insrc/domain/graph/builder/stages/build-edges.tsdoes not currently includeinterface, so emitted relations still won't materialize as graph edges until that filter is extended — that's a broader cross-language change worth tracking separately.Closes #1115
Test plan
cargo test -p codegraph-core --lib extractors::groovy— newextracts_interface_inheritancetest passes alongside existing 8 testsnpx vitest run tests/parsers/groovy.test.ts— newextracts interface inheritance (extends_interfaces)test passes alongside existing 6 testsnpx biome check src/extractors/groovy.ts tests/parsers/groovy.test.ts— clean